package com.eyas.core.annotation;

import com.eyas.core.annotation.check.CheckAnnotationUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.ClassMetadata;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.filter.TypeFilter;

import java.io.IOException;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
 * @Auther: 王龙龙
 * @Date: 2018/11/30 11:31
 * @Description:
 */
public class JdbcTemplateModelFilter implements TypeFilter {
    private final static Logger logger = LoggerFactory.getLogger(JdbcTemplateModelFilter.class);
    @Override
    public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
        long start = System.currentTimeMillis();
        AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
        ClassMetadata classMetadata = metadataReader.getClassMetadata();
        try {
            Class modelClass = Class.forName(classMetadata.getClassName(),false,this.getClass().getClassLoader());
            Set<String> annotation_set = annotationMetadata.getAnnotationTypes();
            if(annotation_set==null||annotation_set.size()==0) {
                return false;
            }
            if(annotation_set.contains("com.eyas.core.annotation.Table")) {
                if(AnnotationUtils.findAnnotation(modelClass,Table.class).value().equals(""))
                    throw new JdbcTemplateScanException("scan jdbc template Entity model:["+classMetadata.getClassName()+"] @Table's value() is null");
                this.checkTableAnnotation(modelClass);
            }else if(annotation_set.contains("com.eyas.core.annotation.Procedure")) {
                if(AnnotationUtils.findAnnotation(modelClass,Procedure.class).value().equals(""))
                    throw new JdbcTemplateScanException("scan jdbc template Entity model:["+classMetadata.getClassName()+"] @Procedure's value() is null");
                this.checkProcedureAnnotation(modelClass);
            }else {
                throw new JdbcTemplateScanException("scan jdbc template Entity model:["+classMetadata.getClassName()+"] is invalid");
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (JdbcTemplateScanException e) {
            throw e;
        }
        long end = System.currentTimeMillis();
        logger.info("Annotation TypeFilter for class["+classMetadata.getClassName()+"]--------------------"+(end-start)+" ms");
        //不放入bean工厂中
        return false;
    }

    /**
     * 校验@Table注解的实体类是否合法
     * @param clazz
     */
    private void checkTableAnnotation(Class clazz) {
        //$PrimaryKey_count用来记录@PrimaryKey的数量，有且只能有1个
        int $PrimaryKey_count = 0;
        //遍历class中所有的属性去查找@PrimaryKey
        for(Field field:clazz.getDeclaredFields()) {
            PrimaryKey primaryKey = AnnotationUtils.findAnnotation(field,PrimaryKey.class);
            Column column = AnnotationUtils.findAnnotation(field,Column.class);

            //当存在@PrimaryKey时，@Column不能存在,但@Sequence可以有，不起作用
            if(primaryKey!=null&&column!=null)
                throw new JdbcTemplateScanException("["+clazz.getName()+"."+field.getName()+"] has both @PrimaryKey and @Column");

            //每找到一个@PrimaryKey，都校验其value是否为空
            if(primaryKey!=null) {
                if($PrimaryKey_count>=1) {
                    throw new JdbcTemplateScanException("["+clazz.getName()+"] @PrimaryKey is more than 1 count");
                }
                //都校验其value是否为空
                if(primaryKey.value().equals(""))
                    throw new JdbcTemplateScanException("["+clazz.getName()+"."+field.getName()+"] @PrimaryKey's value() can not null");

                //并且当inrementType是SEQUENCE的时候，还需要有@Sequence
                if(primaryKey.inrementType()==IncrementType.SEQUENCE) {
                    Sequence sequence = AnnotationUtils.findAnnotation(field,Sequence.class);
                    if(sequence==null||sequence.value().equals("")) {
                        throw new JdbcTemplateScanException("["+clazz.getName()+"."+field.getName()+"] need @Sequence and Sequence's value() can not null");
                    }
                }
                $PrimaryKey_count++;

                //校验columnType和field Type是否能对应上
                CheckAnnotationUtils.validType(clazz,field,primaryKey.columnType());

            }else if(column!=null&&column.value().equals("")) {//每找到一个@Column，校验value不能为空
                if(column.value().equals(""))
                    throw new JdbcTemplateScanException("["+clazz.getName()+"."+field.getName()+"] @Column's value() can not null");
                CheckAnnotationUtils.validType(clazz,field,primaryKey.columnType());
            }

        }
        //判断$PrimaryKey_count，不等于1的话就是有问题，目前不支持联合主键
        if($PrimaryKey_count!=1)
            throw new JdbcTemplateScanException("["+clazz.getName()+"] @PrimaryKey's number is not 1");

    }


    /**
     * 校验存储过程是否合法
     * @param clazz
     */
    private void checkProcedureAnnotation(Class clazz) {
        //integer:param index；string:field name
        //用于记录是否存在index重复的情况
        Map<Integer,String> map = new HashMap<>();
        //遍历class下所有属性，没有属性执行无参数存过
        for(Field field:clazz.getDeclaredFields()) {
            ProcedureType paramType = AnnotationUtils.findAnnotation(field,ProcedureType.class);
            if(paramType!=null) {
                if(paramType.paramIndex()<1)
                    throw new JdbcTemplateScanException("["+clazz.getName()+"."+field.getName()+"] paramIndex can not be less than 1 ");

                if(map.put(paramType.paramIndex(),field.getName())!=null)
                    throw new JdbcTemplateScanException("["+clazz.getName()+"."+field.getName()+"] paramIndex repetition exists ");

                //校验paramType的数据类型是否合理
                CheckAnnotationUtils.validType(clazz,field,paramType.columnType());
            }
        }
    }

}
